package com.mdp.workflow.de.service;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.mdp.core.entity.Result;
import com.mdp.core.entity.Tips;
import com.mdp.core.err.BizException;
import com.mdp.core.service.BaseService;
import com.mdp.safe.client.entity.User;
import com.mdp.workflow.api.ModelService;
import com.mdp.workflow.de.entity.Model;
import com.mdp.workflow.de.entity.ModelHistory;
import com.mdp.workflow.de.entity.ModelRelation;
import com.mdp.workflow.de.mapper.ModelMapper;
import com.mdp.workflow.pub.Const;
import org.apache.commons.lang3.StringUtils;
import org.flowable.bpmn.converter.BpmnXMLConverter;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.bpmn.model.ExtensionElement;
import org.flowable.bpmn.model.Process;
import org.flowable.bpmn.model.UserTask;
import org.flowable.editor.language.json.converter.BpmnJsonConverter;
import org.flowable.editor.language.json.converter.util.CollectionUtils;
import org.flowable.editor.language.json.converter.util.JsonConverterUtil;
import org.flowable.ui.modeler.domain.AbstractModel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;

/**
 * 父类已经支持增删改查操作,因此,即使本类什么也不写,也已经可以满足一般的增删改查操作了.<br> 
 * 组织 com.mdp  顶级模块 workflow 大模块 de 小模块 <br>
 * 实体 Model 表 act_de_model 当前主键(包括多主键): id; 
 ***/
@Service("mdp.workflow.de.ModelServiceImpl")
public class ModelServiceImpl extends BaseService<ModelMapper,Model> implements ModelService {
	static Logger logger =LoggerFactory.getLogger(ModelServiceImpl.class);

	/**
	 * 自定义查询，支持多表关联
	 * @param page 分页条件
	 * @param ew 一定要，并且必须加@Param("ew")注解
	 * @param ext 如果xml中需要根据某些值进行特殊处理，可以通过这个进行传递，非必须，注解也可以不加
	 * @return
	 */
	public List<Map<String,Object>> selectListMapByWhere(IPage page, QueryWrapper ew, Map<String,Object> ext){
		return baseMapper.selectListMapByWhere(page,ew,ext);
	}


	public static final String NAMESPACE = "http://activiti.com/modeler";

	protected static final String PROCESS_NOT_FOUND_MESSAGE_KEY = "PROCESS.ERROR.NOT-FOUND";

	@Autowired
	protected ModelImageService modelImageService;


	@Autowired
	protected ModelHistoryService modelHistoryService;

	@Autowired
	protected ModelRelationService modelRelationService;

	@Autowired
	protected ObjectMapper objectMapper;



	protected BpmnJsonConverter bpmnJsonConverter = new BpmnJsonConverter();

	protected BpmnXMLConverter bpmnXMLConverter = new BpmnXMLConverter();
 
	public Model getModel(String modelId) {
		Model p=new Model();
		p.setId(modelId);
		Model model = this.selectOneObject(p) ;

		if (model == null) {
			throw new BizException(PROCESS_NOT_FOUND_MESSAGE_KEY,"没有找到对应的模型");
		}

		return model;
	}


	public List<Model> getModelsByModelType(Integer modelType) {
		Model p=new Model();
		p.setModelType(modelType);
		return this.selectListByWhere(p);
	}


	public ModelHistory getModelHistory(String modelId, String modelHistoryId) {
		// Check if the user has read-rights on the process-model in order to fetch history
		Model model = getModel(modelId);
		ModelHistory mh=new ModelHistory();
		mh.setId(modelHistoryId);
		ModelHistory modelHistory = (ModelHistory) modelHistoryService.getById(modelHistoryId);

		// Check if history corresponds to the current model and is not deleted
		if (modelHistory == null || modelHistory.getRemovalDate() != null || !modelHistory.getModelId().equals(model.getId())) {
			throw new BizException(PROCESS_NOT_FOUND_MESSAGE_KEY,"没有找到对应的模型");
		}
		return modelHistory;
	}
 
	public byte[] getBpmnXML(Model model) {
		BpmnModel bpmnModel = getBpmnModel(model);
		return getBpmnXML(bpmnModel);
	}
 
	public byte[] getBpmnXML(BpmnModel bpmnModel) {
		for (Process process : bpmnModel.getProcesses()) {
			if (StringUtils.isNotEmpty(process.getId())) {
				char firstCharacter = process.getId().charAt(0);
				// no digit is allowed as first character
				if (Character.isDigit(firstCharacter)) {
					process.setId("a" + process.getId());
				}
			}
		}
		byte[] xmlBytes = bpmnXMLConverter.convertToXML(bpmnModel);
		return xmlBytes;
	}

	public Result validateModelKey(Model model, Integer modelType, String key) {
		Model p=new Model();
		p.setModelKey(key);
		p.setModelKey(key);

		List<Model> models = this.selectListByWhere(p);
		for (Model modelInfo : models) {
			if (model == null || modelInfo.getId().equals(model.getId()) == false) {
				return Result.error("模型已经存在"); 
			}
		}

		return Result.ok();
	}
 
	@Transactional
	public Model createModel(Model newModel, User createdBy) {
		newModel.setVersion(1);
		newModel.setCreated(Calendar.getInstance().getTime());
		newModel.setCreatedBy(createdBy.getUserid());
		newModel.setLastUpdated(Calendar.getInstance().getTime());
		newModel.setLastUpdatedBy(createdBy.getUserid());

		persistModel(newModel);
		return newModel;
	}
 
	@Transactional
	public Model createModel(Model model, String editorJson, User createdBy) {
		Model newModel = model;
		newModel.setVersion(1);
		newModel.setModelEditorJson(editorJson);
		newModel.setLastUpdated(Calendar.getInstance().getTime());
		newModel.setLastUpdatedBy(createdBy.getUserid());
		newModel.setCreated(new Date());
		newModel.setTenantId(createdBy.getBranchId());
		newModel.setCreatedBy(createdBy.getUserid());
		persistModel(newModel);
		return newModel;
	}
 
	@Transactional
	public Model createNewModelVersion(Model modelObject, String comment, User updatedBy) {
		modelObject.setLastUpdated(new Date());
		modelObject.setLastUpdatedBy(updatedBy.getUserid());
		modelObject.setCreatedBy(updatedBy.getUserid());
		modelObject.setTenantId(updatedBy.getBranchId());
		modelObject.setModelComment(comment);   ModelHistory historyModel = createNewModelhistory(modelObject);
		persistModelHistory(historyModel);   modelObject.setVersion(modelObject.getVersion() + 1);
		persistModel(modelObject);   return modelObject;
	}
 
	@Transactional
	public ModelHistory createNewModelVersionAndReturnModelHistory(Model modelObject, String comment, User updatedBy) {
		modelObject.setLastUpdated(new Date());
		modelObject.setLastUpdatedBy(updatedBy.getUserid());   modelObject.setTenantId(updatedBy.getBranchId());
		modelObject.setModelComment(comment);   ModelHistory historyModel = createNewModelhistory(modelObject);
		persistModelHistory(historyModel);   modelObject.setVersion(modelObject.getVersion() + 1);
		persistModel(modelObject);   return historyModel;
	}



	public Model saveModel(Model modelObject) {
		return persistModel(modelObject);
	}


	@Transactional
	public Model saveModel(Model modelObject, String editorJson, byte[] imageBytes, boolean newVersion, String newVersionComment, User updatedBy) {

		return internalSave(modelObject.getName(), modelObject.getModelKey(), modelObject.getDescription(), editorJson, newVersion,
				newVersionComment, imageBytes, updatedBy, modelObject);
	}


	@Transactional
	public Model saveModel(String modelId, String name, String key, String description, String editorJson,
													  boolean newVersion, String newVersionComment, User updatedBy) {
		Model p=new Model();
		p.setId(modelId);
		Model modelObject = this.selectOneObject(p);
		return internalSave(name, key, description, editorJson, newVersion, newVersionComment, null, updatedBy, modelObject);
	}

	protected Model internalSave(String name, String key, String description, String editorJson, boolean newVersion,
															String newVersionComment, byte[] imageBytes, User updatedBy, Model modelObject) {

		if (newVersion == false) {

			modelObject.setLastUpdated(new Date());
			modelObject.setLastUpdatedBy(updatedBy.getUserid());
			modelObject.setName(name);
			modelObject.setModelKey(key);
			modelObject.setDescription(description);
			modelObject.setModelEditorJson(editorJson);
			modelObject.setTenantId(updatedBy.getBranchId());
			if (imageBytes != null) {
				modelObject.setThumbnail(imageBytes);
			}

		} else {

			ModelHistory historyModel = createNewModelhistory(modelObject);
			persistModelHistory(historyModel);

			modelObject.setVersion(modelObject.getVersion() + 1);
			modelObject.setLastUpdated(new Date());
			modelObject.setLastUpdatedBy(updatedBy.getUserid());
			modelObject.setName(name);
			modelObject.setModelKey(key);
			modelObject.setDescription(description);
			modelObject.setModelEditorJson(editorJson);
			modelObject.setModelComment(newVersionComment);

			modelObject.setTenantId(updatedBy.getBranchId());

			if (imageBytes != null) {
				modelObject.setThumbnail(imageBytes);
			}
		}

		return persistModel(modelObject);
	}


	@Transactional
	public void deleteModel(String modelId, boolean cascadeHistory, boolean deleteRuntimeApp) {
		Model p=new Model();
		p.setId(modelId);
		Model model = this.selectOneObject(p);
		if (model == null) {
			throw new IllegalArgumentException("No model found with id: " + modelId);
		}

		// Fetch current model history list
		List<ModelHistory> history = modelHistoryService.findByModelIdAndRemovalDateIsNullOrderByVersionDesc(model.getId());

		// if the model is an app definition and the runtime app needs to be deleted, remove it now
		if (deleteRuntimeApp && model.getModelType() == Const.MODEL_TYPE_APP) {
			/**
			 String appDefinitionId = appDefinitionService.getDefinitionIdForModelAndUser(model.getId(), SecurityUtils.getCurrentUserObject());
			 if (appDefinitionId != null) {
			 deploymentService.deleteAppDefinition(appDefinitionId);
			 }
			 **/
		} else {
			// Move model to history and mark removed
			ModelHistory historyModel = createNewModelhistory(model);
			historyModel.setRemovalDate(Calendar.getInstance().getTime());
			persistModelHistory(historyModel);
		}

		if (cascadeHistory || history.size() == 0) {
			deleteModelAndChildren(model);
		} else {
			// History available and no cascade was requested. Revive latest history entry
			ModelHistory toRevive = history.remove(0);
			populateModelBasedOnHistory(model, toRevive);
			persistModel(model);
			modelHistoryService.deleteByPk(toRevive);
		}
	}

	protected void deleteModelAndChildren(Model model) {

		// Models have relations with each other, in all kind of wicked and funny ways.
		// Hence, we remove first all relations, comments, etc. while collecting all models.
		// Then, once all foreign key problemmakers are removed, we remove t
		ModelRelation modelRelation=new  ModelRelation();
		modelRelation.setParentModelId(model.getId());
		modelRelationService.deleteByWhere(modelRelation);
		this.deleteByPk(model);
	}



	@Transactional
	public Tips reviveProcessModelHistory(ModelHistory modelHistory, User user, String newVersionComment) {
		Model p=new Model();
		p.setId(modelHistory.getModelId());
		Model latestModel = this.selectOneObject(p);
		if (latestModel == null) {
			throw new IllegalArgumentException("No process model found with id: " + modelHistory.getModelId());
		}

		// Store the current model in history
		ModelHistory latestModelHistory = createNewModelhistory(latestModel);
		persistModelHistory(latestModelHistory);

		// Populate the actual latest version with the properties in the historic model
		latestModel.setVersion(latestModel.getVersion() + 1);
		latestModel.setLastUpdated(new Date());
		latestModel.setLastUpdatedBy(user.getUserid());
		latestModel.setName(modelHistory.getName());
		latestModel.setModelKey(modelHistory.getModelKey());
		latestModel.setDescription(modelHistory.getDescription());
		latestModel.setModelEditorJson(modelHistory.getModelEditorJson());
		latestModel.setModelType(modelHistory.getModelType());
		latestModel.setModelComment(newVersionComment);
		persistModel(latestModel);

		return new Tips("成功");

	}


	public BpmnModel getBpmnModel(Model model) {
		BpmnModel bpmnModel = null;
		try {
			Map<String, Model> formMap = new HashMap<String, Model>();
			Map<String, Model> decisionTableMap = new HashMap<String, Model>();
			/**
			 List<Model> referencedModels = modelRelationService.findModelsByParentModelId(model.getId());
			 for (Model childModel : referencedModels) {
			 if (Model.MODEL_TYPE_FORM == childModel.getModelType()) {
			 formMap.put(childModel.getId(), childModel);

			 } else if (Model.MODEL_TYPE_DECISION_TABLE == childModel.getModelType()) {
			 decisionTableMap.put(childModel.getId(), childModel);
			 }
			 }
			 **/
			bpmnModel = getBpmnModel(model, formMap, decisionTableMap);

		} catch (Exception e) {
			log.error("Could not generate BPMN 2.0 model for " + model.getId(), e);
			throw new BizException("Could not generate BPMN 2.0 model");
		}

		return bpmnModel;
	}



	public BpmnModel getBpmnModel(Model model, Map<String, Model> formMap, Map<String, Model> decisionTableMap) {
		try {
			ObjectNode editorJsonNode = (ObjectNode) objectMapper.readTree(model.getModelEditorJson());
			Map<String, String> formKeyMap = new HashMap<String, String>();
			for (Model formModel : formMap.values()) {
				formKeyMap.put(formModel.getId(), formModel.getModelKey());
			}

			Map<String, String> decisionTableKeyMap = new HashMap<String, String>();
			for (Model decisionTableModel : decisionTableMap.values()) {
				decisionTableKeyMap.put(decisionTableModel.getId(), decisionTableModel.getModelKey());
			}

			return bpmnJsonConverter.convertToBpmnModel(editorJsonNode, formKeyMap, decisionTableKeyMap);

		} catch (Exception e) {
			log.error("Could not generate BPMN 2.0 model for " + model.getId(), e);
			throw new BizException("Could not generate BPMN 2.0 model");
		}
	}

	protected void addOrUpdateExtensionElement(String name, String value, UserTask userTask) {
		List<ExtensionElement> extensionElements = userTask.getExtensionElements().get(name);

		ExtensionElement extensionElement;

		if (CollectionUtils.isNotEmpty(extensionElements)) {
			extensionElement = extensionElements.get(0);
		} else {
			extensionElement = new ExtensionElement();
		}
		extensionElement.setNamespace(NAMESPACE);
		extensionElement.setNamespacePrefix("modeler");
		extensionElement.setName(name);
		extensionElement.setElementText(value);

		if (CollectionUtils.isEmpty(extensionElements)) {
			userTask.addExtensionElement(extensionElement);
		}
	}

	public Long getModelCountForUser(User user, int modelType) {
		Model p=new Model();
		p.setCreatedBy(user.getUserid());
		p.setModelType(modelType);
		return this.countByWhere(p);
	}

	protected Model persistModel(Model model) {


		if (StringUtils.isNotEmpty(model.getModelEditorJson())) {

			// Parse json to java
			ObjectNode jsonNode = null;
			try {
				jsonNode = (ObjectNode) objectMapper.readTree(model.getModelEditorJson());
			} catch (Exception e) {
				log.error("Could not deserialize json model", e);
				throw new BizException("序列化json出错");
			}

			if ((model.getModelType() == null || model.getModelType().intValue() == Const.MODEL_TYPE_BPMN)) {

				// Thumbnail
				modelImageService.generateThumbnailImage(model, jsonNode);


				// Relations
				//handleBpmnProcessFormModelRelations(model, jsonNode);
				//handleBpmnProcessDecisionTaskModelRelations(model, jsonNode);

			}
		}
		if(StringUtils.isEmpty(model.getId())){
			model.setId(this.createKey("id"));
			this.insert(model);
		}else{
			this.updateByPk(model);
		}

		return model;
	}

	protected ModelHistory persistModelHistory(ModelHistory modelHistory) {
		if(StringUtils.isEmpty(modelHistory.getId())){
			modelHistory.setId(this.createKey("id"));
			modelHistoryService.insert(modelHistory);
		}else{
			modelHistoryService.updateByPk(modelHistory);
		}

		return modelHistory;
	}

	protected void handleBpmnProcessFormModelRelations(Model bpmnProcessModel, ObjectNode editorJsonNode) {
		List<JsonNode> formReferenceNodes = JsonConverterUtil.filterOutJsonNodes(JsonConverterUtil.getBpmnProcessModelFormReferences(editorJsonNode));
		Set<String> formIds = JsonConverterUtil.gatherStringPropertyFromJsonNodes(formReferenceNodes, "id");

		handleModelRelations(bpmnProcessModel, formIds, Const.TYPE_FORM_MODEL_CHILD);
	}

	protected void handleBpmnProcessDecisionTaskModelRelations(Model bpmnProcessModel, ObjectNode editorJsonNode) {
		List<JsonNode> decisionTableNodes = JsonConverterUtil.filterOutJsonNodes(JsonConverterUtil.getBpmnProcessModelDecisionTableReferences(editorJsonNode));
		Set<String> decisionTableIds = JsonConverterUtil.gatherStringPropertyFromJsonNodes(decisionTableNodes, "id");

		handleModelRelations(bpmnProcessModel, decisionTableIds, Const.TYPE_DECISION_TABLE_MODEL_CHILD);
	}

	protected void handleAppModelProcessRelations(Model appModel, ObjectNode appModelJsonNode) {
		Set<String> processModelIds = JsonConverterUtil.getAppModelReferencedModelIds(appModelJsonNode);
		handleModelRelations(appModel, processModelIds, Const.TYPE_PROCESS_MODEL);
	}

	/**
	 * Generic handling of model relations: deleting/adding where needed.
	 */
	protected void handleModelRelations(Model bpmnProcessModel, Set<String> idsReferencedInJson, String relationshipType) {

		// Find existing persisted relations
		List<ModelRelation> persistedModelRelations = modelRelationService.findByParentModelIdAndType(bpmnProcessModel.getId(), relationshipType);

		// if no ids referenced now, just delete them all
		if (idsReferencedInJson == null || idsReferencedInJson.size() == 0) {
			modelRelationService.removeByIds(persistedModelRelations);
			return;
		}

		Set<String> alreadyPersistedModelIds = new HashSet<String>(persistedModelRelations.size());
		for (ModelRelation persistedModelRelation : persistedModelRelations) {
			if (!idsReferencedInJson.contains(persistedModelRelation.getModelId())) {
				// model used to be referenced, but not anymore. Delete it.
				modelRelationService.deleteByPk((ModelRelation) persistedModelRelation);
			} else {
				alreadyPersistedModelIds.add(persistedModelRelation.getModelId());
			}
		}

		// Loop over all referenced ids and see which one are new
		for (String idReferencedInJson : idsReferencedInJson) {

			// if model is referenced, but it is not yet persisted = create it
			if (!alreadyPersistedModelIds.contains(idReferencedInJson)) {

				// Check if model actually still exists. Don't create the relationship if it doesn't exist. The client UI will have cope with this too.
				if (this.exists(idReferencedInJson)) {
					ModelRelation mr=new ModelRelation();
					mr.setId(modelRelationService.createKey("id"));
					mr.setParentModelId(bpmnProcessModel.getId());
					mr.setModelId(idReferencedInJson);
					mr.setRelationType(relationshipType);
					modelRelationService.insert(mr);
				}
			}
		}
	}

	private boolean exists(String idReferencedInJson) {
		// TODO Auto-generated method stub
		return false;
	}

	protected ModelHistory createNewModelhistory(Model model) {
		ModelHistory historyModel = new ModelHistory();
		historyModel.setName(model.getName());
		historyModel.setModelKey(model.getModelKey());
		historyModel.setDescription(model.getDescription());
		historyModel.setCreated(model.getCreated());
		historyModel.setLastUpdated(model.getLastUpdated());
		historyModel.setCreatedBy(model.getCreatedBy());
		historyModel.setLastUpdatedBy(model.getLastUpdatedBy());
		historyModel.setModelEditorJson(model.getModelEditorJson());
		historyModel.setModelType(model.getModelType());
		historyModel.setVersion(model.getVersion());
		historyModel.setModelId(model.getId());
		historyModel.setModelComment(model.getModelComment());

		return historyModel;
	}

	protected void populateModelBasedOnHistory(Model model, ModelHistory basedOn) {
		model.setName(basedOn.getName());
		model.setModelKey(basedOn.getModelKey());
		model.setDescription(basedOn.getDescription());
		model.setCreated(basedOn.getCreated());
		model.setLastUpdated(basedOn.getLastUpdated());
		model.setCreatedBy(basedOn.getCreatedBy());
		model.setLastUpdatedBy(basedOn.getLastUpdatedBy());
		model.setModelEditorJson(basedOn.getModelEditorJson());
		model.setModelType(basedOn.getModelType());
		model.setVersion(basedOn.getVersion());
		model.setModelComment(basedOn.getModelComment());
	}


	public AbstractModel getAbstractModel(String modelId) {
		Model m=this.getModel(modelId);
		AbstractModel abm=new AbstractModel();
		abm.setKey(m.getModelKey());
		abm.setId(m.getId());
		abm.setComment(m.getModelComment());
		abm.setCreated(m.getCreated());
		abm.setCreatedBy(m.getCreatedBy());
		abm.setDescription(m.getDescription());
		abm.setLastUpdated(m.getLastUpdated());
		abm.setLastUpdatedBy(m.getLastUpdatedBy());
		abm.setModelEditorJson(m.getModelEditorJson());
		abm.setModelType(m.getModelType());
		abm.setName(m.getName());
		abm.setTenantId(m.getTenantId());
		return null;
	}
}

